#include "GlesCube.h"
#include "fragment.h"
#include "vertex.h"
#include <math.h>


using namespace Osp::Base;
using namespace Osp::Base::Runtime;
using namespace Osp::Graphics;
using namespace Osp::Locales;
using namespace Osp::System;
using namespace Osp::App;
using namespace Osp::System;
using namespace Osp::Ui;
using namespace Osp::Ui::Controls;
using namespace Osp::Graphics::Opengl;

#define TIME_OUT		10
#define PI				3.1415926535897932384626433832795f
//#define DISPLAY_FPS


class GlesForm :
	public Osp::Ui::Controls::Form
{
public:
	GlesForm(GlesCube* pApp)
	: __pApp(pApp)
	{
	}
	virtual ~GlesForm(void)
	{
	}

public:
	virtual result OnDraw(void)
	{
		if (__pApp)
		{
			__pApp->Draw();
		}
		return E_SUCCESS;
	}

private:
	GlesCube* __pApp;
};


const GLfloat vertices[] = {
	-0.5f, -0.5f, -0.5f,
	-0.5f, -0.5f,  0.5f,
	 0.5f, -0.5f,  0.5f,
	 0.5f, -0.5f, -0.5f,
	-0.5f,  0.5f, -0.5f,
	-0.5f,  0.5f,  0.5f,
	 0.5f,  0.5f,  0.5f,
	 0.5f,  0.5f, -0.5f,
	-0.5f, -0.5f, -0.5f,
	-0.5f,  0.5f, -0.5f,
	 0.5f,  0.5f, -0.5f,
	 0.5f, -0.5f, -0.5f,
	-0.5f, -0.5f,  0.5f,
	-0.5f,  0.5f,  0.5f,
	 0.5f,  0.5f,  0.5f,
	 0.5f, -0.5f,  0.5f,
	-0.5f, -0.5f, -0.5f,
	-0.5f, -0.5f,  0.5f,
	-0.5f,  0.5f,  0.5f,
	-0.5f,  0.5f, -0.5f,
	 0.5f, -0.5f, -0.5f,
	 0.5f, -0.5f,  0.5f,
	 0.5f,  0.5f,  0.5f,
	 0.5f,  0.5f, -0.5f
};

const GLshort indices[] = {
	 0,  2,  1,
	 0,  3,  2, 
	 4,  5,  6,
	 4,  6,  7,
	 8,  9, 10,
	 8, 10, 11, 
	12, 15, 14,
	12, 14, 13, 
	16, 17, 18,
	16, 18, 19, 
	20, 23, 22,
	20, 22, 21
};
const int numIndices = 36;

const GLfloat colors[] = {	
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0, 
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0,
	1.0, 0.0, 0.0, 1.0,
	0.0, 1.0, 0.0, 1.0,
	0.0, 0.0, 1.0, 1.0, 
};


GlesCube::GlesCube()
	: __eglDisplay(EGL_DEFAULT_DISPLAY),
	  __eglSurface(EGL_NO_SURFACE),
	  __eglConfig(null),
	  __eglContext(EGL_NO_DISPLAY),	 
	  __programObject(0),
	  __idxPosition(0),
	  __idxColor(0),
	  __idxMVP(0),
	  __angle(0.0f),
	  __pTimer(null),
	  __pForm(null)
{	
}


GlesCube::~GlesCube()
{
}


void 
GlesCube::Cleanup()
{
	if (__pTimer) 
	{
		__pTimer->Cancel();
		delete __pTimer;
		__pTimer = null;
	}

	DestroyGL();
}


Application*
GlesCube::CreateInstance(void)
{
	return new GlesCube();
}


bool
GlesCube::OnAppInitializing(AppRegistry& appRegistry)
{
	result r = E_SUCCESS;
	IAppFrame* pAppFrame = GetAppFrame();

	__pForm = new GlesForm(this);
	if (null == __pForm)
	{
		AppLog("[GlesCube] Allocation of GlesForm has failed.\n");
		goto CATCH_2;
	}

	r = __pForm->Construct(FORM_STYLE_NORMAL);
	if (IsFailed(r))
	{
		AppLog("[GlesCube] __pForm->Construct(FORM_STYLE_NORMAL) has failed.\n");
		goto CATCH_1;
	}

	r = GetAppFrame()->GetFrame()->AddControl(*__pForm);
	if (IsFailed(r))
	{
		AppLog("[GlesCube] GetAppFrame()->GetFrame()->AddControl(*__pForm) has failed.\n");
		goto CATCH_1;
	}

	if (null == pAppFrame)
	{
		AppLog("[GlesCube] GlesCube::GetAppFrame() has failed.\n");
		goto CATCH_2;
	}

	r = pAppFrame->AddKeyEventListener(*this);
	if (IsFailed(r))
	{
		AppLog("[GlesCube] pAppFrame->AddKeyEventListener(*this) has failed.\n");
		goto CATCH_2;
	}

	if (!InitEGL()) 
	{
		AppLog("[GlesCube] GlesCube::InitEGL() has failed.\n");
		goto CATCH_2;
	}

	if (!InitGL())
	{
		AppLog("[GlesCube] GlesCube::InitGL() has failed.\n");
		goto CATCH_2;
	}

	__pTimer = new Timer;
	if (null == __pTimer)
	{
		goto CATCH_2;
	}
	r = __pTimer->Construct(*this);
	if (IsFailed(r))
	{
		goto CATCH_2;
	}	

	// Comment the following statement to stop listen to the screen on/off events.
	PowerManager::SetScreenEventListener(*this);

	return true;

CATCH_1:
	delete __pForm;
	__pForm = null;

CATCH_2:
	Cleanup();
	return false;
}


bool
GlesCube::OnAppTerminating(AppRegistry& appRegistry, bool forcedTermination)
{
	Cleanup();

	return true;
}


void
GlesCube::OnForeground(void)
{
	if(__pTimer)
	{
		__pTimer->Start(TIME_OUT);
	}
}


void
GlesCube::OnBackground(void)
{
	if(__pTimer)
	{
		__pTimer->Cancel();
	}
}


void
GlesCube::OnBatteryLevelChanged(BatteryLevel batteryLevel)
{
}


void
GlesCube::OnLowMemory(void)
{
}

void
GlesCube::OnKeyPressed(const Control& source, KeyCode keyCode)
{
}


void
GlesCube::OnKeyReleased(const Control& source, KeyCode keyCode)
{	
}


void
GlesCube::OnKeyLongPressed(const Control& source, KeyCode keyCode)
{
}

void 
GlesCube::OnTimerExpired(Timer& timer) 
{	
	if (!__pTimer)
	{
		return;
	}

	__pTimer->Start(TIME_OUT);

	Update();

	if (!Draw())
	{
		AppLog("[GlesCube] GlesCube::Draw() has failed.\n");
	}	
}


bool 
GlesCube::InitEGL()
{
	EGLint numConfigs = 1;
	EGLint eglConfigList[] = {
		EGL_RED_SIZE,	5,
		EGL_GREEN_SIZE,	6,
		EGL_BLUE_SIZE,	5,
		EGL_ALPHA_SIZE,	0,
		EGL_DEPTH_SIZE, 8,
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
		EGL_NONE
	};	
	EGLint eglContextList[] = {
		EGL_CONTEXT_CLIENT_VERSION, 2, 
		EGL_NONE
	};

	eglBindAPI(EGL_OPENGL_ES_API);

	if (__eglDisplay) 
	{
		DestroyGL();
	}

	__eglDisplay = eglGetDisplay((EGLNativeDisplayType)EGL_DEFAULT_DISPLAY);
	if (EGL_NO_DISPLAY == __eglDisplay)
	{		
		AppLog("[GlesCube] eglGetDisplay() is failed. [0x%x]\n", eglGetError());
		goto CATCH;
	}

	if (EGL_FALSE == eglInitialize(__eglDisplay, null, null) ||
		EGL_SUCCESS != eglGetError())
	{		
		AppLog("[GlesCube] eglInitialize() has been failed. [0x%x]\n", eglGetError());
		goto CATCH;
	}
	
	if (EGL_FALSE == eglChooseConfig(__eglDisplay, eglConfigList, &__eglConfig, 1, &numConfigs) || 
		EGL_SUCCESS != eglGetError())
	{		
		AppLog("[GlesCube] eglChooseConfig() has been failed. [0x%x]\n", eglGetError());
		goto CATCH;
	}

	if (!numConfigs)
	{		
		AppLog("[GlesCube] eglChooseConfig() has been failed. because of matching config doesn't exist \n");
		goto CATCH;
	}

	__eglSurface = eglCreateWindowSurface(__eglDisplay, __eglConfig, (EGLNativeWindowType)__pForm, null);

 	if (EGL_NO_SURFACE == __eglSurface ||
		EGL_SUCCESS != eglGetError())
	{		
		AppLog("[GlesCube] eglCreateWindowSurface() has been failed. EGL_NO_SURFACE [0x%x]\n", eglGetError());
		goto CATCH;
	}

	__eglContext = eglCreateContext(__eglDisplay, __eglConfig, EGL_NO_CONTEXT, eglContextList);
	if (EGL_NO_CONTEXT == __eglContext || 
		EGL_SUCCESS != eglGetError())
	{		
		AppLog("[GlesCube] eglCreateContext() has been failed. [0x%x]\n", eglGetError());
		goto CATCH;
	}

	if (EGL_FALSE == eglMakeCurrent(__eglDisplay, __eglSurface, __eglSurface, __eglContext) || 
		EGL_SUCCESS != eglGetError())
	{		
		AppLog("[GlesCube] eglMakeCurrent() has been failed. [0x%x]\n", eglGetError());
		goto CATCH;
	}
	
	return true;

CATCH:
	{
		AppLog("[GlesCube] GlesCube can run on systems which supports OpenGL ES(R) 2.0.");
		AppLog("[GlesCube] When GlesCube does not correctly execute, there are a few reasons.");
		AppLog("[GlesCube]    1. The current device(real-target or simulator) does not support OpenGL ES(R) 2.0. Check the Release Notes.");
		AppLog("[GlesCube]    2. The system running on simulator cannot support OpenGL(R) 2.1 or later. Try with other system.");
		AppLog("[GlesCube]    3. The system running on simulator does not maintain the latest graphics driver. Update the graphics driver.");
	}

	DestroyGL();
	return false;
}


bool 
GlesCube::InitGL()
{
	GLint linked = GL_FALSE;
	GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
	GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);

	GLint numFormats = 0;
	glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &numFormats);

	GLboolean shaderCompiler = GL_FALSE;
	glGetBooleanv(GL_SHADER_COMPILER, &shaderCompiler);

	if (0 < numFormats)
	{
		GLboolean compiled = GL_FALSE;
		GLint* formats = new GLint[numFormats];
		glGetIntegerv(GL_SHADER_BINARY_FORMATS, formats);
		for (GLint i = 0; i < numFormats; i++)
		{
			glShaderBinary(1, &fragShader, formats[i] , fragment, fragment_size);
			if (glGetError())
			{
				continue;
			}
			glShaderBinary(1, &vertShader, formats[i] , vertex, vertex_size);
            if (glGetError())
			{
				continue;
			}
			compiled = GL_TRUE;
			break;
		}
		delete [] formats;

		if (GL_FALSE == compiled)
		{
			goto CATCH;
		}
	}
	else if (GL_TRUE == shaderCompiler)
	{
		glShaderSource(fragShader, 1, (const char**)&fragmentText, null);
		glCompileShader(fragShader);
		GLint bShaderCompiled = GL_FALSE;
		glGetShaderiv(fragShader, GL_COMPILE_STATUS, &bShaderCompiled);
		if (GL_FALSE == bShaderCompiled)
		{
			goto CATCH;		
		}

		glShaderSource(vertShader, 1, (const char**)&vertexText, null);
		glCompileShader(vertShader);
		glGetShaderiv(vertShader, GL_COMPILE_STATUS, &bShaderCompiled);
		if (GL_FALSE == bShaderCompiled)
		{
			goto CATCH;
		}
	}
	else
	{
		goto CATCH;
	}

	__programObject = glCreateProgram();
	glAttachShader(__programObject, fragShader);
	glAttachShader(__programObject, vertShader);
	glLinkProgram(__programObject);
	glGetProgramiv(__programObject, GL_LINK_STATUS, &linked);
	if (GL_FALSE == linked)
	{
		GLint infoLen = 0;
		glGetProgramiv(__programObject, GL_INFO_LOG_LENGTH, &infoLen);
		if (1 < infoLen)
		{			
			char* infoLog = new char[infoLen];
			glGetProgramInfoLog(__programObject, infoLen, null, infoLog);
			AppLog("[GlesCube] Linking has failed. log: %s\n", infoLog);
			delete [] infoLog;
		}
		
		goto CATCH;	
	}

	__idxPosition = glGetAttribLocation(__programObject, "a_position");
	__idxColor = glGetAttribLocation(__programObject, "a_color");
	__idxMVP = glGetUniformLocation(__programObject, "u_mvpMatrix");

	glUseProgram(__programObject);

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glEnable(GL_DEPTH_TEST);

	__angle = 45.0f;
	memset(&__matMVP, 0, sizeof(Matrix));

	glDeleteShader(vertShader);
	glDeleteShader(fragShader);

	glClearColor(0, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	return true;

CATCH:
	glDeleteShader(vertShader);
	glDeleteShader(fragShader);
	
	return false;
}


void 
GlesCube::DestroyGL()
{
	if (__programObject)
	{
		glDeleteProgram(__programObject);
	}
	
	if (__eglDisplay)
	{
		eglMakeCurrent(__eglDisplay, null, null, null);
		
		if (__eglContext)
		{
			eglDestroyContext(__eglDisplay, __eglContext);
			__eglContext = EGL_NO_CONTEXT;
		}
		
		if (__eglSurface) 
		{
			eglDestroySurface(__eglDisplay, __eglSurface);
			__eglSurface = EGL_NO_SURFACE;
		}
		
		eglTerminate(__eglDisplay);
		__eglDisplay = EGL_NO_DISPLAY;
	}

	return;
}


void
GlesCube::Update()
{
	Matrix matPerspective;
	Matrix matModelview;
	float  aspect;

	__angle += 5.0f;
	if (360.0f <= __angle)
	{
		__angle -= 360.0f;
	}

	int x, y, width, height;
	GetAppFrame()->GetFrame()->GetBounds(x, y, width, height);
	aspect = (GLfloat) width / (GLfloat) height;

	LoadIdentity(&matPerspective);
	Perspective(&matPerspective, 60.0f, aspect, 1.0f, 20.0f);

	LoadIdentity(&matModelview);

	Translate(&matModelview, 0.0f, 0.0f, -2.5f);

	Rotate(&matModelview, __angle, 1.0f, 0.0f, 1.0f);

	Multiply(&__matMVP, &matModelview, &matPerspective);
}


bool
GlesCube::Draw()
{
	if (GL_FALSE == eglMakeCurrent(__eglDisplay, __eglSurface, __eglSurface, __eglContext) ||
		EGL_SUCCESS != eglGetError())
	{
		return false;
	}

	int x, y, width, height;
	GetAppFrame()->GetFrame()->GetBounds(x, y, width, height);
	glViewport(0, 0, width, height);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glVertexAttribPointer(__idxPosition, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GL_FLOAT), vertices);
	glVertexAttribPointer(__idxColor, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GL_FLOAT), colors);

	glEnableVertexAttribArray(__idxPosition);
	glEnableVertexAttribArray(__idxColor);

	glUniformMatrix4fv(__idxMVP, 1, GL_FALSE, (GLfloat*) &__matMVP.m[0][0]);

	glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, indices);

	glFinish();

	eglSwapBuffers(__eglDisplay, __eglSurface);	

#ifdef DISPLAY_FPS
	static float     fps = 0.0f;
	static float     updateInterval = 1000.0f;
	static float     timeSinceLastUpdate = 0.0f;
	static float     frameCount = 0;
	static long long currentTick;
	static long long lastTick;
	static bool      isFirst = true;

	if (isFirst)
	{
		SystemTime::GetTicks(currentTick);
		lastTick = currentTick;
		isFirst = false;
	}

	frameCount++;
	SystemTime::GetTicks(currentTick);
	
	float elapsed = currentTick - lastTick;
	
	lastTick = currentTick;
	timeSinceLastUpdate += elapsed;
	
	if (timeSinceLastUpdate > updateInterval)
	{
		if (timeSinceLastUpdate)
		{
			fps = (frameCount / timeSinceLastUpdate) * 1000.f;
			AppLog("[GlesCube] FPS: %f frames/sec\n", fps);
			
			frameCount = 0;
			timeSinceLastUpdate -= updateInterval;
		}
	}
#endif

	return true;
}


void 
GlesCube::LoadIdentity(Matrix* result)
{
	memset(result, 0x0, sizeof(Matrix));
	result->m[0][0] = 1.0f;
	result->m[1][1] = 1.0f;
	result->m[2][2] = 1.0f;
	result->m[3][3] = 1.0f;
}


void 
GlesCube::Frustum(Matrix *result, float left, float right, float bottom, float top, float nearZ, float farZ)
{
    float	deltaX = right - left;
    float	deltaY = top - bottom;
    float	deltaZ = farZ - nearZ;
    Matrix	frustum;

    if ((nearZ <= 0.0f) || (farZ <= 0.0f) ||
        (deltaX <= 0.0f) || (deltaY <= 0.0f) || (deltaZ <= 0.0f)) 
	{
         return;
	}

    frustum.m[0][0] = 2.0f * nearZ / deltaX;
    frustum.m[0][1] = frustum.m[0][2] = frustum.m[0][3] = 0.0f;

    frustum.m[1][1] = 2.0f * nearZ / deltaY;
    frustum.m[1][0] = frustum.m[1][2] = frustum.m[1][3] = 0.0f;

    frustum.m[2][0] = (right + left) / deltaX;
    frustum.m[2][1] = (top + bottom) / deltaY;
    frustum.m[2][2] = -(nearZ + farZ) / deltaZ;
    frustum.m[2][3] = -1.0f;

    frustum.m[3][2] = -2.0f * nearZ * farZ / deltaZ;
    frustum.m[3][0] = frustum.m[3][1] = frustum.m[3][3] = 0.0f;

    Multiply(result, &frustum, result);
}


void 
GlesCube::Perspective(Matrix* result, float fovy, float aspect, float nearZ, float farZ)
{
	GLfloat frustumW, frustumH;
   	
	frustumH = tanf(fovy / 360.0f * PI) * nearZ;
	frustumW = frustumH * aspect;

	Frustum(result, -frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
}


void 
GlesCube::Translate(Matrix *result, GLfloat tx, GLfloat ty, GLfloat tz)
{
	result->m[3][0] += (result->m[0][0] * tx + result->m[1][0] * ty + result->m[2][0] * tz);
    result->m[3][1] += (result->m[0][1] * tx + result->m[1][1] * ty + result->m[2][1] * tz);
    result->m[3][2] += (result->m[0][2] * tx + result->m[1][2] * ty + result->m[2][2] * tz);
    result->m[3][3] += (result->m[0][3] * tx + result->m[1][3] * ty + result->m[2][3] * tz);
}


void 
GlesCube::Rotate(Matrix *result, GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
	GLfloat sinAngle, cosAngle;
	GLfloat mag = sqrtf(x * x + y * y + z * z);
	 
	sinAngle = sinf(angle * PI / 180.0f);
	cosAngle = cosf(angle * PI / 180.0f);
	
	if (0.0f < mag)
	{
		GLfloat xx, yy, zz, xy, yz, zx, xs, ys, zs;
		GLfloat oneMinusCos;
		Matrix  rotMat;

		x /= mag;
		y /= mag;
		z /= mag;

		xx = x * x;
		yy = y * y;
		zz = z * z;
		xy = x * y;
		yz = y * z;
		zx = z * x;
		xs = x * sinAngle;
		ys = y * sinAngle;
		zs = z * sinAngle;
		oneMinusCos = 1.0f - cosAngle;

		rotMat.m[0][0] = (oneMinusCos * xx) + cosAngle;
		rotMat.m[0][1] = (oneMinusCos * xy) - zs;
		rotMat.m[0][2] = (oneMinusCos * zx) + ys;
		rotMat.m[0][3] = 0.0F; 

		rotMat.m[1][0] = (oneMinusCos * xy) + zs;
		rotMat.m[1][1] = (oneMinusCos * yy) + cosAngle;
		rotMat.m[1][2] = (oneMinusCos * yz) - xs;
		rotMat.m[1][3] = 0.0F;

		rotMat.m[2][0] = (oneMinusCos * zx) - ys;
		rotMat.m[2][1] = (oneMinusCos * yz) + xs;
		rotMat.m[2][2] = (oneMinusCos * zz) + cosAngle;
		rotMat.m[2][3] = 0.0F; 

		rotMat.m[3][0] = 0.0F;
		rotMat.m[3][1] = 0.0F;
		rotMat.m[3][2] = 0.0F;
		rotMat.m[3][3] = 1.0F;

		Multiply(result, &rotMat, result);
	}
}


void 
GlesCube::Multiply(Matrix *result, Matrix *srcA, Matrix *srcB)
{
	Matrix  tmp;

	for (int i = 0; i < 4; i++)
	{
		tmp.m[i][0] = (srcA->m[i][0] * srcB->m[0][0]) +
		              (srcA->m[i][1] * srcB->m[1][0]) +
		              (srcA->m[i][2] * srcB->m[2][0]) +
		              (srcA->m[i][3] * srcB->m[3][0]) ;

		tmp.m[i][1] = (srcA->m[i][0] * srcB->m[0][1]) + 
		              (srcA->m[i][1] * srcB->m[1][1]) +
		              (srcA->m[i][2] * srcB->m[2][1]) +
		              (srcA->m[i][3] * srcB->m[3][1]) ;

		tmp.m[i][2] = (srcA->m[i][0] * srcB->m[0][2]) + 
		              (srcA->m[i][1] * srcB->m[1][2]) +
		              (srcA->m[i][2] * srcB->m[2][2]) +
		              (srcA->m[i][3] * srcB->m[3][2]) ;

		tmp.m[i][3] = (srcA->m[i][0] * srcB->m[0][3]) + 
		              (srcA->m[i][1] * srcB->m[1][3]) +
		              (srcA->m[i][2] * srcB->m[2][3]) +
		              (srcA->m[i][3] * srcB->m[3][3]) ;
	}

    memcpy(result, &tmp, sizeof(Matrix));
}

void
GlesCube::OnScreenOn (void)
{
	if(__pTimer)
	{
		__pTimer->Start(TIME_OUT);
	}
}

void
GlesCube::OnScreenOff (void)
{
	if(__pTimer)
	{
		__pTimer->Cancel();
	}
}

